package com.zkh.myframe.server;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

/**
 * jar包加载器
 * @author zkh
 */
public class JarInJarClassLoader {
	
	/**
	 * 主方法
	 * @param args 参数
	 */
	public static void main(String[] args) throws Exception{
		// 当前应用路径（如果是文件.jar，则是jar包运行，否则为本地运行）
		String path = URLDecoder.decode(JarInJarClassLoader.class.getProtectionDomain().getCodeSource().getLocation().getPath(), "UTF-8");
		//File对象
		File file = new File(path);
		//如果是文件，则加载依赖jar包
		if(file.isFile()) {
			//临时目录（所有的jar包都备份到该目录）
			File temp = new File(new File(System.getProperty("java.io.tmpdir")), "myframe.temp.lib");
			//创建目录
			if(!temp.isDirectory() && temp.mkdir()) {
				System.out.println("创建临时目录：" + temp.getAbsolutePath());
			}
			//获取jar包URL
			List<File> jarList = getJarList(file, temp);
			//加载jar包
			loadJars(jarList);
			//加入全局属性
			System.setProperty("java.class.path", jarList.stream().map(File::getAbsolutePath).collect(Collectors.joining(";")));
		}
		//获取真正主方法
		String main = getRealMainClass(path);
		//获取运行类Class
		Class<?> cls = Class.forName(main);
		//使用反射调用真正主方法的main方法
		cls.getMethod("main", String[].class).invoke(null, (Object) args);
	}
	
	/**
	 * 获取jar包列表
	 * @param file 运行jar文件
	 */
	private static List<File> getJarList(File file, File temp) throws IOException{
		//容器
		List<File> jarList = new ArrayList<>();
		// 读取jar包
		try(JarFile jarFile = new JarFile(file)){
			// 遍历jar中的文件
			Enumeration<JarEntry> items = jarFile.entries();
			// 开始遍历
			while (items.hasMoreElements()) {
				// 获取当前元素
				JarEntry entry = items.nextElement();
				//名称
				String name = entry.getName();
				//判断文件
				if(name.matches("lib/[\\w\\-.]+\\.jar")) {
					//临时jar文件
					File tempJar = new File(temp, name.substring(name.lastIndexOf('/')+1));
					//如果存在，先删除
					if(tempJar.isFile()) {
						tempJar.delete();
					}
					//创建文件
					tempJar.createNewFile();
					//写入数据
					saveInputStreamTo(jarFile.getInputStream(entry), tempJar);
					//保存
					jarList.add(tempJar);
				}
			}
		}
		//返回
		return jarList;
	}

	/**
	 * 将文件的InputStream保存到指定文件
	 * @param is 要保存的流
	 * @param file 保存文件
	 */
	public static void saveInputStreamTo(InputStream is, File file) throws IOException{
		//创建输出流
		try(FileOutputStream os = new FileOutputStream(file)) {
			//创建字节数组
			byte[] bytes = new byte[1024];
			//数据长度
			int len = 0;
			//循环遍历
			while((len=is.read(bytes))>0){
				os.write(bytes, 0, len);
			}
		}
	}
	
	/**
	 * 获取真正的主方法
	 */
	private static String getRealMainClass(String path) throws IOException{
		//读取MANIFEST.MF文件（同名文件会读取到很多，需要过滤本项目的文件）
		Enumeration<URL> iter = ClassLoader.getSystemClassLoader().getResources("META-INF/MANIFEST.MF");
		//遍历
		while(iter.hasMoreElements()) {
			//当前URL
			URL url = iter.nextElement();
			//获取当前path
			String currPath = url.getPath();
			//是当前项目的配置
			if(currPath.contains(path)) {
				//数据容器
				Map<String, String> map = new HashMap<>();
				//文件
				readFile(url.openStream(), (line, content)->{
					//分割
					String[] kv = content.split(": ");
					//解析
					if(kv.length>=2) {
						map.put(kv[0].trim(), kv[1].trim());
					}
				});
				//获取真正的主方法并返回
				return map.get("Real-Main-Class");
			}
		}
		//未找到配置
		return null;
	}
	
	/**
	 * 读取文件内容
	 * @param inputStream 输入流
	 * @param reader 自定义读方法
	 */
	private static void readFile(InputStream inputStream, BiConsumer<Integer, String> reader) throws IOException{
		try(//读取文件句柄
			InputStreamReader isr = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
			BufferedReader br = new BufferedReader(isr)){
			//行记录
			int count = 0;
			//临时数据
			String tmp;
			//处理文件
			while((tmp=br.readLine())!=null){
				reader.accept(++count, tmp);
			}
		}
	}
	
	/**
	 * 加载jar包
	 * @param jarList 依赖jar包
	 */
	private static void loadJars(List<File> jarList) throws Exception {
		// 添加依赖包路径
		Method method = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
		// 保存方法权限，使用后再恢复
		boolean accessible = method.isAccessible();
		// 设置为可访问
		method.setAccessible(true);
		// 获取系统类加载器
		URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
		// 遍历临时jar包
		jarList.forEach(e -> {
			try {
				// 将jar包添加到类加载器
				method.invoke(classLoader, e.toURI().toURL());
			} catch (Exception e1) {
				e1.printStackTrace();
			}
		});
		// 还原方法权限
		method.setAccessible(accessible);
	}
}
